package es.udc.empresauriostic.geofinder.model;

import java.io.IOException;
import java.util.List;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;

import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Log;
import android.util.Pair;

import com.google.android.maps.GeoPoint;

import es.udc.empresauriostic.geofinder.R;
import es.udc.empresauriostic.geofinder.model.utils.Util;

/**
 * Implements all server API calls.
 * 
 * @author Santiago Munín González
 * 
 */
public class ServerAPI {
	private static String TAG = "Server API";
	private static String SERVER_URL = "http://10.0.2.2:8080";
	private static String API_VERSION = "v1";
	// TODO SET
	private static int CONNECTION_TIMEOUT = 7500;
	private static String LOGIN_OP = "login";
	private static String REGISTER_OP = "register";
	private static String ASK_PUBLIC_GAME_LIST_OP = "games";
	private static String JOIN_GAME_OP = "join";
	private static String SUBMIT_LOC_OP = "location";
	private static String ASK_FOR_NOTIFICATIONS_OP = "notifications";
	private static String ASK_FOR_ALL_CLUES_OP = "hints";
	private static String ASK_FOR_GAME_STATUS_OP = "game_status";
	private static String ASK_FOR_SCORES_OP = "scores";
	private static String OK = "OK";
	private static String LOCATION_FOUND = "0";
	private static String LOCATION_CLOSE = "1";
	private static String LOCATION_MEDIUM_DISTANCE = "2";
	private static String LOCATION_FAR = "3";
	private static String LOCATION_WIN = "5";

	// TODO ids in responses

	/**
	 * Represents all possible responses after a location submission.
	 * 
	 * @author Santiago Munín González
	 * 
	 */
	public enum SubmittedLocationResponse {
		FOUND, CLOSE, MEDIUM, FAR, WIN;
		/**
		 * Parses a {@link String} and returns a
		 * {@link SubmittedLocationResponse}.
		 * 
		 * @param code
		 *            String value.
		 * @return {@link SubmittedLocationResponse}.
		 */
		public static SubmittedLocationResponse getFromCode(String code) {
			if (code.equals(LOCATION_FOUND)) {
				return FOUND;
			}
			if (code.equals(LOCATION_CLOSE)) {
				return CLOSE;
			}
			if (code.equals(LOCATION_MEDIUM_DISTANCE)) {
				return MEDIUM;
			}
			if (code.equals(LOCATION_FAR)) {
				return FAR;
			}
			if (code.equals(LOCATION_WIN)) {
				return WIN;
			}
			return FAR;
		}
	}

	/**
	 * Performs an HTTP get request.
	 * 
	 * @param context
	 *            Application context.
	 * @param callParameters
	 *            Call's parameters.
	 * @return {@link String} containing the response.
	 * @throws IOException
	 *             If there was any problem.
	 */
	private static String httpRequest(Context context, String callParameters)
			throws IOException {
		SharedPreferences preferences = context.getSharedPreferences(
				context.getString(R.string.const_string_preferences),
				Activity.MODE_PRIVATE);
		String email = preferences.getString("email", "");
		String password = preferences.getString("user_password", "");
		String url = SERVER_URL + "/" + API_VERSION + "/" + email + "/"
				+ password + "/" + callParameters;
		Log.i(TAG, "sent: " + url);

		HttpClient client = new DefaultHttpClient();
		HttpGet get = new HttpGet(url);
		HttpResponse responseGet = client.execute(get);
		HttpEntity resEntityGet = responseGet.getEntity();
		if (resEntityGet != null) {
			// do something with the response
			String response = EntityUtils.toString(resEntityGet);
			Log.i("GET RESPONSE", response);
			return response;
		}
		return "";
	}

	/**
	 * Logs an user in.
	 * 
	 * @param context
	 *            Application context.
	 * @return <b>true</b> if user was correctly logged.
	 * @throws IOException
	 *             If there was any problem.
	 */
	// FIXME fix doc
	public static Pair<Long, Long> login(Context context) throws IOException {
		String response = httpRequest(context, LOGIN_OP);
		if (response.startsWith(OK)) {
			String data[] = response.split(";");
			Long userId = Long.valueOf(data[1]);
			Long GameId = data.length > 2 ? Long.valueOf(data[2]) : -1;
			return new Pair<Long, Long>(userId, GameId);
		} else
			return new Pair<Long, Long>((long) -1, (long) -1);
	}

	/**
	 * Registers a new user.
	 * 
	 * @param context
	 *            Application context.
	 * @param mail
	 *            Player's email.
	 * @param userName
	 *            Player's user name.
	 * @param password
	 *            Player's password (SHA-1).
	 * @return <b>true</b> if user was correctly registered.
	 * @throws IOException
	 *             If there was any problem.
	 */
	// FIXME fix doc
	public static long register(Context context, String mail, String userName,
			String password) throws IOException {
		String response = httpRequest(context, REGISTER_OP + "/" + userName);

		if (response.startsWith(OK)) {
			return Long.valueOf(response.substring(3));
		} else
			return -1;
	}

	/**
	 * Get a range of public games near the player's zone. Implements
	 * page-by-page iterator.
	 * 
	 * @param context
	 *            Application context.
	 * @param location
	 *            Player's location coordinates.
	 * @param offset
	 *            First element of response.
	 * @param size
	 *            List's size.
	 * @return A list of {@link Game} objects.
	 * @throws IOException
	 *             If there was any problem.
	 */
	public static List<Game> getPublicGames(Context context, GeoPoint location,
			int offset, int size) throws IOException {
		String response = httpRequest(context, ASK_PUBLIC_GAME_LIST_OP + "/"
				+ location.getLatitudeE6() + "/" + location.getLongitudeE6()
				+ "/" + offset + "/" + size);
		return Util.parseGameList(response);
	}

	/**
	 * Joins into a game.
	 * 
	 * @param context
	 *            Application context.
	 * @param gameId
	 *            Current game's id.
	 * @return <b>true</b> if correctly logged in.
	 * @throws IOException
	 *             If there was any problem.
	 */
	public static boolean joinGame(Context context, long gameId)
			throws IOException {
		return joinGame(context, gameId, "");
	}

	/**
	 * Joins into a private game.
	 * 
	 * @param context
	 *            Application context.
	 * @param gameId
	 *            Current game's id.
	 * @param gamePassword
	 *            Game's password (SHA-1).
	 * @return <b>true</b> if correctly logged in.
	 * @throws IOException
	 *             If there was any problem.
	 */
	public static boolean joinGame(Context context, long gameId,
			String gamePassword) throws IOException {
		String response = httpRequest(context, JOIN_GAME_OP + "/" + gameId
				+ "/" + gamePassword);
		return response.equals(OK);
	}

	/**
	 * Submits a location.
	 * 
	 * @param context
	 *            Application context.
	 * @param location
	 *            Player's location coordinates.
	 * @return A {@link SubmittedLocationResponse} with response's information.
	 * @throws IOException
	 *             If there was any problem.
	 */
	public static Pair<SubmittedLocationResponse, String> submitLocation(
			Context context, GeoPoint location) throws IOException {
		String response = httpRequest(
				context,
				SUBMIT_LOC_OP + "/" + location.getLatitudeE6() + "/"
						+ location.getLongitudeE6());
		String message = response.length() == 1 ? "" : response.substring(2);
		return new Pair<ServerAPI.SubmittedLocationResponse, String>(
				SubmittedLocationResponse.getFromCode(response.substring(0, 1)),
				message);

	}

	/**
	 * Requests game status.
	 * 
	 * @param context
	 *            Application context.
	 * @return A {@link GameGlobalStatus} with information about the game.
	 * @throws IOException
	 *             If there was any problem.
	 */
	public static GameGlobalStatus getGameStatus(Context context)
			throws IOException {
		String response = httpRequest(context, ASK_FOR_GAME_STATUS_OP);
		return Util.parseGameStatus(response);
	}

	/**
	 * Requests game events (other players progress).
	 * 
	 * @param context
	 *            Application context.
	 * @param lastRequestTimestamp
	 *            Timestamp of last request in order to not get repeated
	 *            information.
	 * @return
	 * @throws IOException
	 *             If there was any problem.
	 */
	public static List<Notification> getGameNotifications(Context context,
			long lastRequestTimestamp) throws IOException {
		String response = httpRequest(context, ASK_FOR_NOTIFICATIONS_OP + "/"
				+ lastRequestTimestamp);
		return Util.parseNotificationList(response);
	}

	/**
	 * Request one more clue.
	 * 
	 * @param context
	 *            Application context.
	 * @return Next clue if exists, "" if there are no more.
	 * @throws IOException
	 *             If there was any problem.
	 */
	public static String getNextClue(Context context) throws IOException {
		// TODO parameter
		return Util.parseClues(
				httpRequest(context, ASK_FOR_ALL_CLUES_OP + "/" + "1")).get(0);
	}

	// TODO comment
	public static List<GameUserData> getHighScores(Context context)
			throws IOException {
		String response = httpRequest(context, ASK_FOR_SCORES_OP);
		return Util.parseScores(
				Util.getPreferences(context).getString("username", "Player"),
				response);

	}

	public static List<String> getCurrentClues(Context context)
			throws IOException {
		String result = httpRequest(context, ASK_FOR_ALL_CLUES_OP + "/" + "0");
		return Util.parseClues(result);
	}

	// TODO comment
}
